///PHILOTES Source Code.  (C)2012 PhiloLabs
#pragma once

#ifndef __InstanceManager_H__
#define __InstanceManager_H__

#include "OgrePrerequisites.h"
#include "OgreMesh.h"
#include "OgreRenderOperation.h"

namespace Ogre
{
	
	

	
	class _OgreExport InstanceManager : public MemAlloc_General
	{
	public:
		enum InstancingTechnique
		{
			ShaderBased,			//Any SM 2.0+ @See InstanceBatchShader
			TextureVTF,				//Needs Vertex Texture Fetch & SM 3.0+ @See InstanceBatchVTF
			HWInstancingBasic,		//Needs SM 3.0+ and HW instancing support @See InstanceBatchHW
			HWInstancingVTF,		//Needs SM 3.0+, HW instancing support & VTF @See InstanceBatchHW_VTF
			InstancingTechniquesCount,
		};

		
		enum BatchSettingId
		{
			/// Makes all batches from same material cast shadows
			CAST_SHADOWS		= 0,
			/// Makes each batch to display it's bounding box. Useful for debugging or profiling
			SHOW_BOUNDINGBOX,

			NUM_SETTINGS
		};

	private:
		struct BatchSettings
		{
			//These are all per material
			bool				setting[NUM_SETTINGS];

			BatchSettings()
			{
				setting[CAST_SHADOWS]				= true;
				setting[SHOW_BOUNDINGBOX]			= false;
			}
		};

		typedef vector<InstanceBatch*>::type		InstanceBatchVec;	//vec[batchN] = Batch
		typedef map<String, InstanceBatchVec>::type	InstanceBatchMap;	//map[materialName] = Vec

		typedef map<String, BatchSettings>::type	BatchSettingsMap;

		const String			mName;					//Not the name of the mesh
		MeshPtr					mMeshReference;
		InstanceBatchMap		mInstanceBatches;
		size_t					mIdCount;

		InstanceBatchVec		mDirtyBatches;

		RenderOperation			mSharedRenderOperation;

		size_t					mInstancesPerBatch;
		InstancingTechnique		mInstancingTechnique;
		uint16					mInstancingFlags;		//@see InstanceManagerFlags
		unsigned short			mSubMeshIdx;
		
		BatchSettingsMap		mBatchSettings;
		SceneManager*			mSceneManager;

		size_t					mMaxLookupTableInstances;
		
		inline InstanceBatch* getFreeBatch( const String &materialName );

		
		InstanceBatch* buildNewBatch( const String &materialName, bool firstTime );

		
		void defragmentBatches( bool optimizeCull, vector<InstancedEntity*>::type &entities,
								InstanceBatchVec &fragmentedBatches );

		
		void applySettingToBatches( BatchSettingId id, bool value, const InstanceBatchVec &container );

	public:
		InstanceManager( const String &customName, SceneManager *sceneManager,
						 const String &meshName, const String &groupName,
						 InstancingTechnique instancingTechnique, uint16 instancingFlags,
						 size_t instancesPerBatch, unsigned short subMeshIdx, bool useBoneMatrixLookup = false);
		virtual ~InstanceManager();

		const String& getName() const { return mName; }

		
		void setInstancesPerBatch( size_t instancesPerBatch );

		
		void setMaxLookupTableInstances( size_t maxLookupTableInstances );

		/**	Calculates the maximum (or the best amount, depending on flags) of instances
			per batch given the suggested size for the technique this manager was created for.
			@remarks
			This is done automatically when creating an instanced entity, but this function in conjunction
			with @see setInstancesPerBatch allows more flexible control over the amount of instances
			per batch
			@param materialName Name of the material to base on
			@param suggestedSize Suggested amount of instances per batch
			@param flags @See InstanceManagerFlags
			@returns The max/best amount of instances per batch given the suggested size and flags
		*/
		size_t getMaxOrBestNumInstancesPerBatch( String materialName, size_t suggestedSize, uint16 flags );

		
		InstancedEntity* createInstancedEntity( const String &materialName );

		
		void cleanupEmptyBatches(void);

		
		void defragmentBatches( bool optimizeCulling );

		
		void setSetting( BatchSettingId id, bool value, const String &materialName = StringUtil::BLANK );

		/// If settings for the given material didn't exist, default value is returned
		bool getSetting( BatchSettingId id, const String &materialName ) const;

		
		bool hasSettings( const String &materialName ) const
		{ return mBatchSettings.find( materialName ) != mBatchSettings.end(); }

		/**	@copydoc InstanceBatch::setStaticAndUpdate */
		void setBatchesAsStaticAndUpdate( bool bStatic );

		
		void _addDirtyBatch( InstanceBatch *dirtyBatch );

		
		void _updateDirtyBatches(void);

		typedef ConstMapIterator<InstanceBatchMap> InstanceBatchMapIterator;
		typedef ConstVectorIterator<InstanceBatchVec> InstanceBatchIterator;

		/// Get non-updateable iterator over instance batches per material
		InstanceBatchMapIterator getInstanceBatchMapIterator(void) const
		{ return InstanceBatchMapIterator( mInstanceBatches.begin(), mInstanceBatches.end() ); }

		
		InstanceBatchIterator getInstanceBatchIterator( const String &materialName ) const
		{
			InstanceBatchMap::const_iterator it = mInstanceBatches.find( materialName );
			return InstanceBatchIterator( it->second.begin(), it->second.end() );
		}
	};
}

#endif
